package me.leon.ade

import java.io.File
import java.io.RandomAccessFile
import java.net.HttpURLConnection
import java.net.URL
import kotlin.concurrent.thread
import kotlin.math.min
import me.leon.ade.NetHelper.ILLEGAL_FILE_NAME_PATTERN

/**
 * 多线程下载 和 断点续传,独立线程配置 采用properties， 主动停止时存取进度，减少I/O
 *
 * feature:
 * 1. 自定义文件名,及目录
 * 2. 自定义线程数
 * 3. 自定义请求头
 * 4. 自定义 bufferSize
 */
class MultiThreadDownloader3
@JvmOverloads
constructor(
    val path: String =
        "http://d0.ananas.chaoxing.com/download/a18af7773cbc71d8f8f4bc204ecabc77?at_=1" +
            "614320013439&ak_=2569f07d4061abb78635a6dfb6b4a198&ad_=33eb94f6ab2aca01" +
            "8df1d8285992b4b8&fn=webKernal.zip",
    val targetDir: String = "/multi/files/",
    var threadCount: Int = 4,
    val headers: MutableMap<String, Any> = mutableMapOf(),
    val bufferSize: Int = 64 * 1024,
    val minBlockSize: Long = 1 * 1024 * 1024,
    val onProgress: (Long, Long, String) -> Unit = { _, _, _ -> },
    val onCompleted: (String) -> Unit = {},
    val onError: (String) -> Unit = {}
) {
    private var completedCount = 0
    private var filePath = ""
    var fileName = ""
    var downloadedBytes = 0L

    var fileSize = 0L
    private val threads: MutableList<DownloadThread> = mutableListOf()
    var isCompleted = false
    var lastDownloadedBytes = 0L
    val showProgressInterval = 500L
    var lastShowTime = 0L
    var debug = true

    /** 下载文件 */
    @Throws(Exception::class)
    fun download() {
        threads.clear()
        completedCount = 0
        downloadedBytes = 0
        if (!File(targetDir).exists()) File(targetDir).mkdirs()
        // 不下载文件 获取文件大小  文件名称
        URL(path).openConnection().safeAs<HttpURLConnection>()?.run {
            requestMethod = "HEAD"
            connectTimeout = 10000
            setRequestProperty("user-agent", NetHelper.COMMON_UA)
            headers.forEach { setRequestProperty(it.key, it.value.toString()) }
            if (responseCode == 200) {
                // 阿里云 文件md5
                //                if (!getHeaderField("etag").isNullOrEmpty()) {
                //                    md5 = getHeaderField("etag").removeSurrounding("\"")
                //                }
                fileName =
                    NetHelper.getNetFileName(this, path)
                        .also { if (debug) println(it) }
                        .replace(ILLEGAL_FILE_NAME_PATTERN.toRegex(), "_")
                        .takeIf { fileName.isNullOrEmpty() }
                        ?: fileName
                if (debug) println(fileName)
                // 在本地创建一个与资源同样大小的文件来占位
                RandomAccessFile(
                        File(targetDir, fileName).also { filePath = it.absolutePath },
                        "rw"
                    )
                    .apply { setLength(contentLengthLong) }
                    .use {
                        // 将下载任务分配给每个线程
                        // println(contentLengthLong)
                        contentLengthLong
                            .also { fileSize = it }
                            .slice(threadCount, minBlockSize)
                            .also { threadCount = it.size }
                            .forEachIndexed { index, longRange ->
                                DownloadThread(index, longRange.first, longRange.last)
                                    .also { threads.add(it) }
                                    .start()
                            }
                    }
            }
        }
    }

    /** 异步下载 */
    fun downloadAsync() {
        thread { download() }
    }

    fun pause() {
        var isStopped = false
        for (thread in threads) {
            if (thread.isAlive) {
                isStopped = true
                thread.stopKindly()
            }
        }
        if (isStopped) println("stop download success")
        else println(" already downloaded, no need to persist!")
    }

    private fun deleteConfigFiles() {
        for (id in 0 until threadCount) File(targetDir, "${fileName}_$id.cfg").delete().also {
            if (debug) println("文件删除 $it")
        }
    }

    // 下载线程
    inner class DownloadThread(val threadId: Int, var startIndex: Long, val endIndex: Long) :
        Thread() {

        var stopped = false

        /**
         * Thread#stop Thread#interept may not works,or occurs errors, suggest to use a exit flag to
         * stop thread
         */
        fun stopKindly() {
            stopped = true
        }

        override fun run() {

            if (debug)
                println("thread $threadId start downloading $startIndex  $endIndex  $fileSize")
            try {
                // 分段请求网络连接,分段将文件保存到本地.
                val configFile = File(targetDir, "${fileName}_$threadId.cfg")
                val configAccessFile: RandomAccessFile
                if (configFile.exists()) {
                    configAccessFile = RandomAccessFile(configFile, "rwd")
                    if (debug) println("${configFile.name} 配置文件存在")

                    configAccessFile.readLine()?.let {
                        startIndex =
                            if (it.isEmpty()) startIndex
                            else
                                it.toLong().also {
                                    println(
                                        "thread $threadId resume..  ${
                                    (it - startIndex).apply {
                                        downloadedBytes += this
                                    }
                                }"
                                    )
                                }
                    }
                } else {
                    configAccessFile = RandomAccessFile(configFile, "rwd")
                }
                startIndex = min(startIndex, endIndex)
                if (debug)
                    println(
                        "thread $threadId start downloading real $startIndex  $endIndex  $fileSize"
                    )

                if (startIndex == endIndex) {
                    configAccessFile.use {
                        completedCount++
                        if (debug) println("thread $threadId already downloaded")
                    }
                    return
                }
                URL(path).openConnection().safeAs<HttpURLConnection>()?.run {
                    requestMethod = "GET"
                    connectTimeout = 10000
                    setRequestProperty("user-agent", NetHelper.COMMON_UA)
                    // 设置分段下载的头信息。  Range:做分段数据请求用的。格式: Range bytes=0-1024  或者 bytes:0-1024
                    setRequestProperty("Range", "bytes=$startIndex-$endIndex")
                    headers.forEach { setRequestProperty(it.key, it.value.toString()) }
                    if (debug) println("thread_$threadId from $startIndex to  $endIndex  $fileSize")
                    if (responseCode == 206) { // 200：请求全部资源成功， 206代表部分资源请求成功
                        val randomAccessFile = RandomAccessFile(File(targetDir, fileName), "rw")
                        randomAccessFile.seek(startIndex)
                        val buffer = ByteArray(bufferSize)
                        var length = -1
                        var total = 0L
                        configAccessFile.use {
                            randomAccessFile.use { raf ->
                                inputStream.use { input ->
                                    while (!stopped &&
                                        input.read(buffer).also { length = it } > 0) {
                                        raf.write(buffer, 0, length)
                                        total += length
                                        downloadedBytes += length
                                        showProgress()
                                        configAccessFile.seek(0)
                                        configAccessFile.write(
                                            (startIndex + total).toString().toByteArray()
                                        )
                                    }
                                }
                            }
                        }
                        // eg.  Range 0-1024, startIndex = 0 , endIndex = 1024,lenght = 1025,
                        // rang starts from 0 , and lenght starts from 1,
                        // so length is next startIndex
                        // if reads 512 byte,request range = 0-511,next startIndex 512,request range
                        //  = 512-1024
                        if (startIndex + total == endIndex + 1) {
                            if (debug) println("线程 $threadId 下载完毕")
                            completedCount++
                        } else {
                            if (debug) println("${startIndex + total}  $endIndex 线程 $threadId 终止")
                        }
                        if (completedCount == threadCount) {
                            isCompleted = true
                            deleteConfigFiles()
                            onCompleted(filePath)
                        }
                    } else {
                        if (debug) println("code $responseCode. 服务器不支持多线程下载")
                        onError("code $responseCode. 服务器不支持多线程下载")
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    @Synchronized
    private fun showProgress() {

        if (System.currentTimeMillis() - lastShowTime < showProgressInterval) {
            if (downloadedBytes == fileSize) {
                val speed =
                    ((downloadedBytes - lastDownloadedBytes).toFloat() /
                            ((System.currentTimeMillis() - lastShowTime)) * 1000)
                        .toLong()
                        .speed()
                if (debug) println("___下载完成: $speed")
                onProgress(downloadedBytes, fileSize, speed)
            }
            return
        }
        if (lastShowTime > 0) {
            val speed =
                ((downloadedBytes - lastDownloadedBytes).toFloat() /
                        ((System.currentTimeMillis() - lastShowTime)) * 1000)
                    .toLong()
                    .speed()
            if (debug) println("下载速度: $speed")
            onProgress(downloadedBytes, fileSize, speed)
        }
        lastDownloadedBytes = downloadedBytes
        lastShowTime = System.currentTimeMillis()

        if (debug)
            println(
                ("$downloadedBytes/$fileSize (${
                String.format(
                    "%.2f%%",
                    100 * downloadedBytes.toFloat() / fileSize
                )
            })")
            )
    }

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            var isComplete = false
            val multiThreadDownloader3 =
                MultiThreadDownloader3(
                        onProgress = { download: Long, fileSize: Long, speed: String ->
                            println(
                                "$download/$fileSize (${
                            String.format(
                                "%.2f%%",
                                100 * download.toFloat() / fileSize
                            )
                        })  $speed"
                            )
                        },
                        onCompleted = {
                            println(
                                "filePath: $it  md5: ${
                            it.fileMd5()
                                .also { println(it + (it == "C8773345B4783FE4AC676C646B1239A9")) }
                        } "
                            )
                            isComplete = true
                        }
                    )
                    .apply { debug = false }
            multiThreadDownloader3.download()

            //            thread {
            //                Thread.sleep(3000)
            //                multiThreadDownloader3.pause()
            //                println("停止")
            //            }
            while (!isComplete) {
                // dead loop
            }
        }
    }
}
